//
// Created by harrybao on 2019/4/3.
//
#include <assert.h>
#include <limits>
#include <numeric>
#include "timer_manager.h"
#include "../../common/container/details/singly_list.h"
#include "../../common/container/details/doubly_list.h"
#include "../../common/define/memory.h"
#include "../../common/pool/object_pool.h"

static const int32_t wheel_count = 4;
static const std::array<int32_t, wheel_count> wheel_bits = { { 10, 8, 8, 6 } };
static const std::array<int32_t, wheel_count + 1> wheel_bits_pre_sum = {{
        0,
        std::accumulate(wheel_bits.begin(), wheel_bits.begin() + 1, 0),
        std::accumulate(wheel_bits.begin(), wheel_bits.begin() + 2, 0),
        std::accumulate(wheel_bits.begin(), wheel_bits.begin() + 3, 0),
        std::accumulate(wheel_bits.begin(), wheel_bits.begin() + 4, 0)
}};
static const std::array<uint32_t, wheel_count> wheel_size = {{
    1u << wheel_bits[0],
    1u << wheel_bits[1],
    1u << wheel_bits[2],
    1u << wheel_bits[3],
}};

#define TIME_WHEEL_POS(time, index) \
    (static_cast<uint32_t>(time >> wheel_bits_pre_sum[index]) & (wheel_size[index] - 1))

class TimerManager::TimerCell
{
public:
    TimerCell() = default;
    ~TimerCell() = default;
public:
    ListNode link_;
    HTIMER uid_{ 0 };
    int64_t interval_{ 0 };
    int64_t next_deadline_{ 0 };
    uint32_t count_{ 0 };

    int32_t test_value_{ 0 };
};

class TimerManager::TimerWheel
{
public:
    void Init(int32_t index)
    {
        index_ = index;
        list_head_ = NEW_ARRAY(ListNode, wheel_size[index_]);
    }
    void UnInit(int32_t index)
    {
        DELETE_ARRAY(list_head_);
        index_ = 0;
    }
    ListNode* GetHead(int32_t pos)
    {
        assert(pos < wheel_size[index_]);
        return &list_head_[pos];
    }
    void InsertTimerCell(TimerCell* timer_cell, int32_t pos)
    {
        assert(pos < wheel_size[index_]);
        DoublyList::Insert(&timer_cell->link_, &list_head_[pos]);
    }
    TimerCell* PopFrontTimerCell(int32_t pos)
    {
        if (DoublyList::Empty(&list_head_[pos]))
        {
            return nullptr;
        }
        assert(pos < wheel_size[index_]);
        ListNode* node = nullptr;
        node = (&list_head_[pos])->next;
        DoublyList::Pop(node);
        return HOST_PTR(node, TimerCell, link_);
    }
private:
    ListNode* list_head_{ nullptr };
    int32_t index_{ 0 };
};


bool TimerManager::Init()
{
    timer_wheels_ = NEW_ARRAY(TimerWheel, wheel_count);
    for (int32_t i = 0; i < wheel_count; ++i)
    {
        timer_wheels_[i].Init(i);
    }
    return true;
}

void TimerManager::UnInit()
{
    for (int32_t i = 0; i < wheel_count; ++i)
    {
        timer_wheels_[i].UnInit(i);
    }
    DELETE_ARRAY(timer_wheels_);
    for (auto &cell : cell_pool_)
    {
        DELETE_OBJECT(cell);
    }
    cell_pool_.clear();
}

void TimerManager::Update(int32_t delta_time)
{
    nex_time_ = now_time_ + delta_time;
    while (now_time_ < nex_time_)
    {
        pos_ = TIME_WHEEL_POS(now_time_, 0);
        if (pos_ == 0)
        {
            UpdateWheel(1);
        }
        TimerCell* cell = nullptr;
        while ((cell = timer_wheels_[0].PopFrontTimerCell(pos_)))
        {
            //TEST
            //printf("value=%d, now_time=%lld\n", cell->test_value_, now_time_);

            if ((--cell->count_) <= 0)
            {
                RecycleTimerCell(cell);
            }
            else
            {
                //TODO
                cell->next_deadline_ += cell->interval_;
                InsertTimer(cell);
            }
        }
        now_time_++;
    }
}

void TimerManager::UpdateWheel(int32_t index)
{
    if (index >= wheel_count)
    {
        return;
    }
    int32_t pos = TIME_WHEEL_POS(now_time_, index);
    auto* head = timer_wheels_[index].GetHead(pos);
    for (ListNode* it = head->next, *t = it->next;
        it != head;
        it = t, t = it->next)
    {
        DoublyList::Pop(it);
        InsertTimer(HOST_PTR(it, TimerCell, link_));
    }
    if (pos == 0)
    {
        UpdateWheel(index + 1);
    }
}


HTIMER TimerManager::AddTimer(int32_t value, int64_t interval, uint32_t count)
{
    auto* cell = GetFreeTimerCell();
    cell->test_value_ = value;
    cell->interval_ = interval;
    cell->next_deadline_ = now_time_ + interval;
    cell->count_ = count;

    InsertTimer(cell);

    return cell->uid_;
}

void TimerManager::KillTimer(HTIMER &timer_uid)
{
    TimerCell* cell = nullptr;
    if (!(cell = FindTimerCell(timer_uid)))
    {
        return;
    }
    DeleteTimer(cell);
    timer_uid = 0;
}

TimerManager::TimerCell* TimerManager::GetFreeTimerCell()
{
    TimerCell* cell = nullptr;
    if (!SinglyList::Empty(free_cell_head_))
    {
        cell = HOST_PTR(SinglyList::Front(free_cell_head_), TimerCell, link_);
        SinglyList::Pop(free_cell_head_);
        cell->uid_ = (((cell->uid_ >> 32) + 1) << 32) | (cell->uid_ & std::numeric_limits<uint32_t>::max());
    }
    else
    {
        cell = NEW_OBJECT(TimerCell);
        cell_pool_.push_back(cell);
        cell->uid_ = 1ull << 32 | cell_pool_.size();
    }
    return cell;
}

void TimerManager::RecycleTimerCell(TimerManager::TimerCell* timer_cell)
{
    SinglyList::Push(&timer_cell->link_, free_cell_head_);
}

void TimerManager::DeleteTimer(TimerManager::TimerCell* timer_cell)
{
    if (timer_cell->link_.IsFree())
    {
        return;
    }
    DoublyList::Pop(&timer_cell->link_);
    RecycleTimerCell(timer_cell);
}

TimerManager::TimerCell *TimerManager::FindTimerCell(HTIMER timer_uid)
{
    auto index = static_cast<size_t>((timer_uid  & std::numeric_limits<uint32_t>::max()) - 1);
    if (index >= cell_pool_.size())
    {
        return nullptr;
    }
    return cell_pool_[index];
}

void TimerManager::InsertTimer(TimerManager::TimerCell *timer_cell)
{
    if (!timer_cell)
    {
        return;
    }
    int64_t delay = timer_cell->next_deadline_ - now_time_;
    int32_t index = 0;
    for (; index < wheel_count; ++index)
    {
        if (static_cast<uint32_t>(delay >> wheel_bits_pre_sum[index]) < wheel_size[index])
        {
            timer_wheels_[index].InsertTimerCell(timer_cell, TIME_WHEEL_POS(timer_cell->next_deadline_, index));
            return;
        }
    }
    index--;
    timer_wheels_[index].InsertTimerCell(timer_cell,
            (static_cast<uint32_t>(now_time_ >> wheel_bits_pre_sum[index]) + (wheel_size[index] - 1)) & (wheel_size[index] - 1));
}

void TimerManager::Debug()
{
    printf("---DEBUG---\n");
    for (int i = 0; i < wheel_count+1; ++i)
    {
        printf("%u ", wheel_bits_pre_sum[i]);
    }
    printf("\n");
    for (int i = 0; i < wheel_count; ++i)
    {
        printf("%u ", wheel_size[i]);
    }
    printf("\n");
    printf("---OVER---\n");
}
